package dropapp

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"time"

	"v8s-api/rest-api/target/generated-sources"
	"v8s-sm-proxy/internal"
	"v8s-sm-proxy/internal/clients/v8s"
	"v8s-sm-proxy/internal/configuration"
	"v8s/logger"

	"go.uber.org/zap"

	"gopkg.in/yaml.v3"
)

type (
	Manager struct {
		log        *zap.SugaredLogger
		v8sClient  *v8s.Client
		properties configuration.V8SProperties
	}

	Task struct {
		RN        internal.ResourceName
		Status    StatusValue
		Endpoints []Endpoint
	}

	Endpoint struct {
		URL         string
		Type        EndpointType
		Description string
	}

	EndpointType string
	StatusValue  string
	Compose      string
)

const (
	resourceNameKey = "resource-name"
	belongsToKey    = "belongs-to"
	belongsToValue  = "smartmarket"

	provisioningStatus StatusValue = "PROVISIONING"
	pausedStatus       StatusValue = "PAUSED"
	readyStatus        StatusValue = "READY"
	errorStatus        StatusValue = "ERROR"
	deletedStatus      StatusValue = "DELETED"
)

func NewManager(v8sClient *v8s.Client, properties configuration.V8SProperties) *Manager {
	return &Manager{
		log: logger.Logger().Named("drop-app-manager").Sugar(),

		v8sClient:  v8sClient,
		properties: properties,
	}
}

func (m *Manager) Create(ctx context.Context, composeData string, projectID internal.ProjectID, rn internal.ResourceName) (*Task, error) {
	composeYAML := map[string]interface{}{}
	err := yaml.Unmarshal([]byte(composeData), &composeYAML)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshall compose from yaml '%s' for resource '%s': %w", composeData, projectID, err)
	}

	composeJSON, err := json.Marshal(composeYAML)
	if err != nil {
		return nil, fmt.Errorf("failed to marshalled to json '%s' for resource '%s': %w", composeYAML, projectID, err)
	}

	resourceName := rn.ToV8SApi()
	request := generated.CreateTaskJSONRequestBody{
		ResourceName: resourceName,
		Project:      projectID.ToV8SProjectID(),
		Compose:      generated.Compose(composeJSON),
		Metadata: &map[string]interface{}{
			belongsToKey: belongsToValue,
		},
	}

	response, err := m.v8sClient.CreateTaskWithResponse(ctx, request)
	if err != nil {
		return nil, fmt.Errorf("failed to create task in project '%s': %w", projectID, err)
	}

	if response.StatusCode() != 200 {
		return nil, fmt.Errorf("failed to create task in project '%s', code '%s', body '%s'", projectID, response.Status(), string(response.Body))
	}

	return &Task{
		RN:     rn,
		Status: provisioningStatus,
	}, nil
}

func (m *Manager) Delete(ctx context.Context, rn internal.ResourceName) error {
	response, err := m.v8sClient.DeleteTaskWithResponse(ctx, rn.ToV8SApi())
	if err != nil {
		return fmt.Errorf("failed to delete task '%s': %w", rn, err)
	}

	if response.StatusCode() == 404 {
		return nil
	}

	if response.StatusCode() >= 200 && response.StatusCode() < 300 {
		return nil
	}

	return fmt.Errorf("failed to delete task '%s': status: '%s', response '%s'", rn, response.Status(), string(response.Body))
}

func (m *Manager) SearchResourcesUpdatedAfter(ctx context.Context, from time.Time, limit int) ([]Task, error) {
	request := generated.SearchTasksJSONRequestBody{
		UpdatedAfter: &from,
		Limit:        &limit,
	}

	response, err := m.v8sClient.SearchTasksWithResponse(ctx, request)
	if err != nil {
		return nil, fmt.Errorf("failed to search tasks updated after '%s'", from)
	}

	if response.StatusCode() != 200 {
		return nil, fmt.Errorf("failed to search tasks updated after '%s': status: '%s', response '%s'", from, response.Status(), string(response.Body))
	}

	items := response.JSON200.Items
	tasks := make([]Task, 0, len(items))

	for _, task := range items {
		current := task
		converted, err := convert(current)
		if err != nil {
			return nil, fmt.Errorf("failed to convert task '%+v', while searching: %w", current, err)
		}

		tasks = append(tasks, converted)
	}

	return tasks, nil
}

func (m *Manager) Suspend(ctx context.Context, rn internal.ResourceName) error {
	response, err := m.v8sClient.SuspendTaskWithResponse(ctx, rn.ToV8SApi())
	if err != nil {
		return fmt.Errorf("cannot suspend resource '%s': %w", rn, err)
	}

	if response.StatusCode() >= 200 && response.StatusCode() < 300 {
		return nil
	}

	return fmt.Errorf("cannot suspend resource '%s': status '%s', response '%s'", rn, response.Status(), string(response.Body))
}

func (m *Manager) Unsuspend(ctx context.Context, rn internal.ResourceName) error {
	response, err := m.v8sClient.ResumeTaskWithResponse(ctx, rn.ToV8SApi())
	if err != nil {
		return fmt.Errorf("cannot unsuspend resource '%s': %w", rn, err)
	}

	if response.StatusCode() >= 200 && response.StatusCode() < 300 {
		return nil
	}

	return fmt.Errorf("cannot unsuspend resource '%s': status '%s', response '%s'", rn, response.Status(), string(response.Body))
}

func (m *Manager) GetTask(ctx context.Context, rn internal.ResourceName) (Task, error) {
	response, err := m.v8sClient.GetTaskWithResponse(ctx, rn.ToV8SApi())
	if err != nil {
		return Task{}, fmt.Errorf("cannot get status of task '%s': %w", rn, err)
	}

	if response.StatusCode() != 200 {
		return Task{}, fmt.Errorf("cannot get status of task '%s': status '%s', response '%s'", rn, response.Status(), string(response.Body))
	}

	task, err := convert(*response.JSON200)
	if err != nil {
		return Task{}, fmt.Errorf("cannot convert response of task '%s': response '%s', err %w", rn, string(response.Body), err)
	}

	return task, nil
}

func (m *Manager) GetCompose(ctx context.Context) (Compose, error) {
	response, err := m.v8sClient.GetComposeSchema(ctx)
	if err != nil {
		return Compose(""), fmt.Errorf("cannot get compose scheme : %w", err)
	}

	defer func() {
		_ = response.Body.Close()
	}()

	data, err := io.ReadAll(response.Body)
	if err != nil {
		return Compose(""), fmt.Errorf("failed to read body from response: %w", err)
	}

	if response.StatusCode != 200 {
		return Compose(""), fmt.Errorf("cannot get compose schema from controller: status '%s', response '%s'", response.Status, string(data))
	}

	return Compose(data), nil
}

func convert(task generated.Task) (Task, error) {
	status, err := convertStatus(task.Status)
	if err != nil {
		return Task{}, fmt.Errorf("failed to convert status '%s': %w", task.Status, err)
	}

	rn, err := internal.ParseResourceNameFromV8SAPI(task.ResourceName)
	if err != nil {
		return Task{}, fmt.Errorf("failed to parse resource name '%s': %w", task.ResourceName, err)
	}

	result := Task{
		RN:     rn,
		Status: status,
	}

	if result.Status == readyStatus {
		endpoints := make([]Endpoint, 0)
		for cluster, clusterDetails := range task.Clusters.AdditionalProperties {
			for service, serviceInfo := range clusterDetails.Services.AdditionalProperties {
				for _, endPoint := range serviceInfo.Endpoints {
					currentURL := endPoint
					currentType := EndpointType(service)
					currentCluster := cluster

					endpoints = append(endpoints, Endpoint{
						URL:         currentURL,
						Type:        currentType,
						Description: fmt.Sprintf("service: '%s', cluster: '%s'", currentType, currentCluster),
					})
				}
			}
		}

		result.Endpoints = endpoints
	}

	return result, nil
}

func convertStatus(status generated.TaskStatus) (StatusValue, error) {
	var result StatusValue

	switch status {
	case generated.TaskStatusResumeRequest:
		result = provisioningStatus
	case generated.TaskStatusCreationRequest:
		result = provisioningStatus
	case generated.TaskStatusSuspensionRequest:
		result = pausedStatus
	case generated.TaskStatusUpdateRequest:
		result = provisioningStatus
	case generated.TaskStatusDeletionRequest:
		result = deletedStatus
	case generated.TaskStatusRunning:
		result = readyStatus
	case generated.TaskStatusSuspended:
		result = pausedStatus
	case generated.TaskStatusError:
		result = errorStatus
	case generated.TaskStatusDeleted:
		result = deletedStatus
	default:
		return StatusValue(""), fmt.Errorf("unknown status '%s'", status)
	}

	return result, nil
}
